package controller;

import model.CreateWaves;
import model.DataHolder;
import model.MoneyOnKill;
import model.OnTouch;
import model.Player;
import model.bullets.IBullet;
import model.enemies.CreateEnemiesThread;
import model.enemies.IEnemy;
import model.surface.Grid;
import model.surface.Map;
import model.surface.Scale;
import model.towers.ITower;
import model.towers.StartShootingThread;
import model.utils.Counter;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.TextView;

import com.example.towerdefence.R;

/**
 * 
 * This is Surface View on which the objects for the game are drawn and updated.
 * 
 * @author EvilTeam
 * 
 */
public class GameBoard extends SurfaceView implements SurfaceHolder.Callback {

	private static final String DELIMITER = "/";
	private static final int MAX_WAVES = 21;
	private static final int STARTING_LIVES = 30;
	private static final int STARTING_MONEY = 1500;
	private static boolean gameOver = false;
	private static int currentLevel = 0;
	private static Player currentPlayer;
	private TextView lives;
	private TextView money;
	private static TextView seconds;
	private TextView monsterType;
	private TextView level;
	private static Thread currentThread;
	private Context context;
	private GameThread gameThread;
	private Bitmap background;
	private boolean areWavesCreated;
	private static boolean runOnceInStartGame = false;
	private static Resources resources;

	public GameBoard(Context context) {
		super(context);
		this.context = context;
		getHolder().addCallback(this);
		setFocusable(true);
		currentPlayer = new Player(STARTING_MONEY, STARTING_LIVES);
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
	}

	/**
	 * Initialize surface view and threads for the game.
	 */
	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		setRes(getResources());
		setBackground(Map.getMapBackground());
		initializeViews();
		if (!areWavesCreated) {
			new CreateWaves();
			for (CreateEnemiesThread thread : DataHolder.getThreads()) {
				thread.start();
			}
			areWavesCreated = true;
		}
		gameThread.start();
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		boolean retry = true;
		gameThread.setRunning(false);
		while (retry) {
			try {
				for (CreateEnemiesThread thread : DataHolder.getThreads())
					thread.join();
				gameThread.join();
				retry = false;
			} catch (InterruptedException e) {
			}
		}
	}

	/**
	 * Starting the game thread when the activity comes in the foreground.
	 */
	public void startUp() {
		Counter.setCountDownCanceled(false);
		gameThread = new GameThread(getHolder(), this);
		for (ITower tower : DataHolder.getTowers()) {
			tower.setStartShootingThread(new StartShootingThread(tower,
					DataHolder.getEnemies()));
			new Thread(tower.getStartShootingThread()).start();
		}
	}

	/**
	 * Stopping the game thread when the activity is in the background.
	 */
	public void shutDown() {
		Counter.setCountDownCanceled(true);
		gameThread.setRunning(false);
		DataHolder.getThreads().clear();
		for (ITower tower : DataHolder.getTowers()) {
			tower.getStartShootingThread().stopShooting();
		}
	}

	/**
	 * Method which draws the elements and the objects on the game board.
	 */
	@Override
	protected void onDraw(Canvas canvas) {
		canvas.drawBitmap(background, 0, 0, null);
		Grid.drawGrid(canvas);
		drawTowers(canvas);
		drawEnemies(canvas);
	}

	/**
	 * Method which handles to update the states of the objects in the game.
	 * 
	 * @param canvas
	 */
	public void updateMyView(Canvas canvas) {
		for (final IEnemy enemy : DataHolder.getEnemies()) {
			if (enemy != null) {
				enemy.clearOldBitmap(canvas);
				enemy.updateMyLocation();
				decreaseLivesIfOut(enemy);
			}
		}
		if (!runOnceInStartGame) {
			delayForStartingNewGame();
			runOnceInStartGame = true;
		}
		setWave();
		nextWave();
		traverseAllBulletsInEachTower(canvas);
		updatePlayersMoney();
		isGameOver();
	}

	/**
	 * The method's sets a counter to delay the start of the game, so the player
	 * can prepare his strategy.
	 */
	private void delayForStartingNewGame() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				((Activity) context).runOnUiThread(new Runnable() {
					@Override
					public void run() {
						Counter.startCountDown(10000);
					}
				});
			}
		}).start();
	}

	/**
	 * The method draws the enemies.
	 * 
	 * @param canvas
	 */
	public void drawEnemies(Canvas canvas) {
		if (!DataHolder.getEnemies().isEmpty()) {
			for (IEnemy enemy : DataHolder.getEnemies()) {
				if (enemy.getHealthPoints() > 0
						&& enemy.getX() <= Scale.getScreenWidth()) {
					enemy.draw(canvas);
				} else {
					if (enemy.getHealthPoints() <= 0) {
						MoneyOnKill.moneyOnKill(enemy);
					}
					DataHolder.getEnemies().remove(enemy);
					enemy = null;
				}
			}
		}
	}

	/**
	 * This method draws the towers.
	 * 
	 * @param canvas
	 */
	public void drawTowers(Canvas canvas) {
		if (!DataHolder.getTowers().isEmpty()) {
			drawAllTowersDependingOnTheirState(canvas);
			for (ITower tower : DataHolder.getTowers()) {
				if (tower.isTouched()) {
					tower.drawRange(canvas);
				}
			}
		}
	}

	/**
	 * Method which handles the touch events on the screen.
	 */
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			OnTouch onTouch = new OnTouch(event.getX(), event.getY(),
					this.context);
			onTouch.onTouch();
			break;
		case MotionEvent.ACTION_MOVE:
			break;
		case MotionEvent.ACTION_UP:
			break;
		}
		return true;
	}

	/**
	 * The method represent information about the next wave to the player.
	 */
	private void nextWave() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				((Activity) context).runOnUiThread(new Runnable() {
					@Override
					public void run() {
						if (currentThread != null) {
							Counter.startCountDown(35000);
							monsterType
									.setText(((CreateEnemiesThread) currentThread)
											.getCurrentEnemy() + "");
						}
					}
				});
			}
		}).start();
	}

	/**
	 * Initializing the view objects in the layout.
	 */
	private void initializeViews() {
		lives = (TextView) ((Activity) context).findViewById(R.id.livesHolder);
		money = (TextView) ((Activity) context).findViewById(R.id.moneyHolder);
		seconds = (TextView) ((Activity) context)
				.findViewById(R.id.holderForNextLevelSeconds);
		monsterType = (TextView) ((Activity) context)
				.findViewById(R.id.holderForNextLevelType);
		level = (TextView) ((Activity) context).findViewById(R.id.levelsHolder);
		lives.setText(currentPlayer.getLives() + "");
		money.setText(currentPlayer.getMoney() + "");
	}

	private void setBackground(Bitmap bitmap) {
		background = Bitmap.createScaledBitmap(bitmap, getWidth(), getHeight(),
				true);
	}

	/**
	 * Shows the game over window.
	 * 
	 * @param image
	 */
	private void gameOverPopWindow(final ImageView image) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				((Activity) context).runOnUiThread(new Runnable() {
					@Override
					public void run() {
						initializePopWindow(image);
					}
				});
			}
		}).start();
	}

	/**
	 * Updates the status for the number of waves in the level.
	 */
	private void setWave() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				((Activity) context).runOnUiThread(new Runnable() {
					@Override
					public void run() {
						level.setText(GameBoard.currentLevel + 1 + DELIMITER
								+ MAX_WAVES);
					}
				});
			}
		}).start();
	}

	/**
	 * Used to decrease the lives of the player if an enemy get to the last
	 * point.
	 * 
	 * @param enemy
	 */
	private void decreaseLivesIfOut(final IEnemy enemy) {
		if (enemy.getX() > Scale.getScreenWidth()) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					((Activity) context).runOnUiThread(new Runnable() {
						@Override
						public void run() {
							currentPlayer.setLives(currentPlayer.getLives() - 1);
							lives.setText(currentPlayer.getLives() + "");
						}
					});
				}
			}).start();
		}
	}

	/**
	 * Used to update the bullets location on the board.
	 * 
	 * @param canvas
	 */
	private void traverseAllBulletsInEachTower(Canvas canvas) {
		for (ITower tower : DataHolder.getTowers()) {
			for (IBullet bullet : tower.getBullets()) {
				bullet.clearOldBitmap(canvas);
				bullet.updateMyLocation();
			}
		}
	}

	/**
	 * Checks if the game is over, and in which state(win or loose) and displays
	 * the proper end game message.
	 */
	private void isGameOver() {
		ImageView image = new ImageView((Activity) context);
		if (currentPlayer.getLives() <= 0 && !gameOver) {
			gameOver = true;
			image.setBackgroundResource(R.drawable.avengers);
			gameOverPopWindow(image);
			DataHolder.clearData();
			Counter.setCountDownCanceled(true);
			startMainMenu();
			((GameBoardActivity)context).onDestroy();
		}
		if (currentLevel >= MAX_WAVES - 1 && !gameOver
				&& DataHolder.getEnemies().isEmpty()) {
			gameOver = true;
			image.setBackgroundResource(R.drawable.endgamevader);
			gameOverPopWindow(image);
			DataHolder.clearData();
			Counter.setCountDownCanceled(true);
			startMainMenu();
			((GameBoardActivity)context).onDestroy();
		}
	}
	
	/**
	 * After the game ends, bring back the main menu.
	 */
	private void startMainMenu() {
		try {
			Thread.sleep(5000);
			Intent startMenuIntent = new Intent(context,
					MenuActivity.class);
			context.startActivity(startMenuIntent);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	/**
	 * This method updates the players money when a tower is set or sold or a
	 * enemy is dead.
	 */
	private void updatePlayersMoney() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				((Activity) context).runOnUiThread(new Runnable() {
					@Override
					public void run() {
						money.setText(currentPlayer.getMoney() + "");
					}
				});

			}
		}).start();
	}

	/**
	 * Draws the towers
	 * 
	 * @param canvas
	 */
	private void drawAllTowersDependingOnTheirState(Canvas canvas) {
		for (ITower tower : DataHolder.getTowers()) {
			if (tower.isSold()) {
				DataHolder.getTowers().remove(tower);
				tower = null;
				updatePlayersMoney();
			} else {
				tower.draw(canvas);
			}
		}
	}

	/**
	 * Initializes the end game pop up window.
	 * 
	 * @param image
	 */
	public void initializePopWindow(ImageView image) {
		FrameLayout layoutOfPopup = (FrameLayout) ((Activity) context)
				.findViewById(R.id.FrameLayout);
		Animation animation = AnimationUtils.loadAnimation(context,
				R.anim.scale);
		image.setAnimation(animation);
		layoutOfPopup.addView(image);
		PopupWindow popupMessage = new PopupWindow(layoutOfPopup,
				LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
		popupMessage.setContentView(layoutOfPopup);
	}

	public static Resources getRes() {
		return resources;
	}

	public static void setRes(Resources res) {
		GameBoard.resources = res;
	}

	public static int getCurrentLevel() {
		return currentLevel;
	}

	public static void setCurrentLevel(int currentLevel) {
		GameBoard.currentLevel = currentLevel;
	}

	public static TextView getSeconds() {
		return seconds;
	}

	public static Player getCurrentPlayer() {
		return currentPlayer;
	}

	public static Thread getCurrentThread() {
		return currentThread;
	}

	public static void setCurrentThread(Thread currentThread) {
		GameBoard.currentThread = currentThread;
	}
}
